package com.pride1952.myoptimusx3;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * BluetoothLeService是该应用中处理蓝牙相关事务的服务。
 * 该服务启动后，并不会自动进行任何操作。
 *
 * 该服务的用法：
 * 用startService()方法即可启动该服务。若要调用服务中的方法，请通过bindService()绑定该服务。若绑定服务，请记得
 * 在绑定该服务的活动停止前调用unbindService解绑。
 *
 * 在服务中留出了外部接口供程序调用，请在调用前阅读方法的注释。外部接口分为设置接口和应用接口，设置接口用来设定相
 * 关参数、进行设置操作，应用接口为读写名片等实际应用。
 *
 * 调用connectDevice()来连接蓝牙设备。若蓝牙未开启或SharedPreference内没有存储的设备地址，会发送一条相应的广
 * 播。若一切顺利，成功连接设备并初始化后会发送一条广播，表示设备处于可用状态。
 *
 * ！！！请注意！！！
 * 在使用任何一个应用接口前，请先调用设置接口中的isReady()方法来验证是否处于可用状态，若是，则可以调用应用接口。
 * 格式例如：
 * if(mBluetoothLeService.isReady()) mBluetoothLeService.doReadIDcard();else 提示用户现在手环未连接或忙碌
 *
 * 若isReady()返回false，说明设备未连接或处于忙碌状态，可通过调用isDeviceConnected()和isDeviceBusy()查看状
 * 态。但此时切勿直接调用connectDevice()来连接设备，而是提示用户设备未连接或忙碌。
 *
 * 一个活动使用该服务的标准流程：
 * 首先调用startService()来启动该服务，并通过bindService取得该服务的实例。获得实例后调用connectDevice()方法
 * 来连接设备并初始化。在初始化结束后服务会发送一条广播，验证isReady()后，调用应用接口如doReadIDcard()。操作完
 * 成后会发送一条广播
 */
public class BluetoothLeService extends Service {

    public static final String mACTION_DEVICE_READY = "com.pride1952.myoptimusx3.mACTION_DEVICE_READY";
    public static final String mACTION_IDCARD_READ = "com.pride1952.myoptimusx3.mACTION_IDCARD_READ";
    public static final String mACTION_IDCARD_WRITE = "com.pride1952.myoptimusx3.mACTION_IDCARD_WRITE";
    public static final String mACTION_BALANCE_READ = "com.pride1952.myoptimusx3.mACTION_BALANCE_READ";
    public static final String mACTION_BALANCE_NOT_READ = "com.pride1952.myoptimusx3.mACTION_BALANCE_NOT_READ";
    public static final String mACTION_REQUEST_ENABLE_BT = "com.pride1952.myoptimusx3.mACTION_REQUEST_ENABLE_BT";
    public static final String mACTION_REQUEST_SCAN_DEVICE = "com.pride1952.myoptimusx3.mACTION_REQUEST_SCAN_DEVICE";
    public static final String mACTION_DEVICE_CONNECTING = "com.pride1952.myoptimusx3.mACTION_DEVICE_CONNECTING";
    public static final String mACTION_DEVICE_CONNECTED = "com.pride1952.myoptimusx3.mACTION_DEVICE_CONNECTED";
    public static final String mACTION_DEVICE_DISCONNECTED = "com.pride1952.myoptimusx3.mACTION_DEVICE_DISCONNECTED";
    public static final String mACTION_BLUETOOTH_BUSY = "com.pride1952.myoptimusx3.mACTION_BLUETOOTH_BUSY";
    public static final String mACTION_BLUETOOTH_AVAILABLE = "com.pride1952.myoptimusx3.mACTION_BLUETOOTH_AVAILABLE";
    public static final String mACTION_DEVICE_NEAR = "com.pride1952.myoptimusx3.mACTION_DEVICE_NEAR";
    public static final String mACTION_DEVICE_NOT_NEAR = "com.pride1952.myoptimusx3.mACTION_DEVICE_NOT_NEAR";
    public static final String mACTION_PASSWORD_CORRECT = "com.pride1952.myoptimusx3.mACTION_PASSWORD_CORRECT";
    public static final String mACTION_PASSWORD_WRONG = "com.pride1952.myoptimusx3.mACTION_PASSWORD_WRONG";
    public static final String mACTION_DEVICE_CONNECT_FAILED = "com.pride1952.myoptimusx3.mACTION_DEVICE_CONNECT_FAILED";

    //可改变常量
    private final static byte DEVICE_PASSWORD_LENGTH = 4;
    private final static byte MAX_WRITE_LOOP = 20;
    private final static long COMMON_TIMEOUT = 15000;
    private final static long COMMON_WAIT_TIME = 500;
    private final static long SCAN_PERIOD = 10000;
    private final static int RSSI_NEAR = -30;
    private final static byte RSSI_SCAN_COUNT = 20;
    private final static long RSSI_SCAN_PERIOD = 500;
    //可改变常量到此结束

///////////////////////////////外部设置接口/////////////////////////////////////////////////////////////

    /**
     * 验证蓝牙服务是否可用。
     * @return true 若蓝牙服务处于可用状态。
     * @return false 若蓝牙服务处于未连接或者忙碌状态。
     */
    public boolean isReady(){
        if(mConnectionState == STATE_CONNECTED && mode == MODE_VOID) return true;
        else return false;
    }

    /**
     * 调用该方法来初始化蓝牙服务。初始化完成后将会通过广播发送mACTION_DEVICE_READY。
     * 若蓝牙服务已经初始化，则直接发送广播。
     * 注意deviceAddress不能为null。
     */
    public void connectDevice(){
        if(isReady()) deviceReady();
        else{
            SharedPreferences mPreferences=getSharedPreferences("Devices", MODE_PRIVATE);
            String mDeviceAddress=mPreferences.getString("address", null);
            String mDevicePassword=mPreferences.getString("password", null);

            connectDevice(mDeviceAddress, mDevicePassword);
        }
    }

    public void connectDevice(String deviceAddress, String devicePassword){
        Log.i(TAG, "Trying to connect device, address=" + deviceAddress + ", password=" + devicePassword);
        if(bluetoothInitialize()) {
            if(deviceAddress != null && devicePassword != null){
                if(setPassword(devicePassword)) connect(deviceAddress);
            }
            else broadcastUpdate(mACTION_REQUEST_SCAN_DEVICE);
        }
    }

    /**
     * 检测是否有设备连接
     * @return true 设备已连接
     */
    public boolean isDeviceConnected(){
        return (mConnectionState == STATE_CONNECTED);
    }

    public int getDeviceState(){
        return mConnectionState;
    }

    /**
     * 检测设备是否忙碌
     * @return true 设备忙碌
     */
    public boolean isDeviceBusy(){
        return (mode != MODE_VOID);
    }

    /**
     * 设置服务内部保存的密码。这个密码在每次连接设备时都需要验证。
     * 请确保这个密码和设备的地址匹配，否则将在连接时返回密码错误。
     * @param psw 密码
     */
    public boolean setPassword(String psw){
        String[] stringArray = new String[psw.length()];
        for(byte i=0; i<psw.length(); i++){
            stringArray[i] = psw.substring(i,i+1);
        }
        if(stringArray.length == DEVICE_PASSWORD_LENGTH) {
            byte[] bytes = new byte[stringArray.length];
            for(byte i=0; i<stringArray.length; i++){
                bytes[i] = Byte.parseByte(stringArray[i]);
            }
            passwordArray = bytes;
            Log.i(TAG, "Password set: " + BytesHandler.bytes2string(passwordArray));
            return true;
        }else {
            Log.e(TAG, "Password length is not " + DEVICE_PASSWORD_LENGTH);
            return false;
        }
    }

//    /**
//     * 返回程序中保存的设备地址（String类型）。若没有保存的设备地址，则返回null。
//     * @return 程序中保存的设备地址
//     */
//    public String getAddress(){
//        if(isAddressSet()) return deviceAddress;
//        else return null;
//    }
//
//    /**
//     * 验证地址是否已设置
//     * @return true 地址已设置
//     * @return false 地址为空
//     */
//    public boolean isAddressSet(){
//        if(deviceAddress == null) return false;
//        else return true;
//    }

////////////////////////外部应用接口/////////////////////////////////////////////////////////////////


    /**
     * 验证已存储的密码
     * @param oriPsw 原密码
     * @return 若密码密码正确返回true
     */
    public boolean doCheckPassword(String oriPsw){
        return BytesHandler.string2bytes(oriPsw) == passwordArray;
    }

    /**
     * 更改设备内存储的密码。
     * @param newPsw 新密码
     */
    public void doChangePassword(String newPsw){
        changePassword(BytesHandler.string2bytes(newPsw));
    }

    /**
     * 发送读取名片的命令。蓝牙服务将在读取名片之后将通过广播发送mACTION_IDCARD_READ。
     * 在收到广播后可以通过调用getIDcards()方法获得读取到的名片。
     */
    public void doReadIDcard(){
        readIDcard();
    }

    /**
     * 获得读取到的名片。
     * @return IDcard[] 即IDcard数组。若没有未读名片，若数组长度为零。
     */
    public IDcard[] doGetIDcards(){
        return readIDcardBuffer.toIDcards();
    }

    /**
     * 发送写名片的命令。
     * 将在发送名片成功后通过广播发送mACTION_IDCARD_WRITE。
     * @param idcard 要发送的名片
     */
    public void doSendIDcard(IDcard idcard){
        Log.i(TAG, "IDcard to send: " + idcard.n);
        SendIDcardBuffer.setBuffer(idcard);
        sendIDcard();
    }

    /**
     * 读取公交卡余额。
     * 将在读取成功后发送广播mACTION_BALANCE_READ,并在intent中附带了读取到的余额（float类型），可通过方法
     * intent.getFloatExtra("balance"，0)获取。
     * 超时时间15s，若在15秒内没有读取到，则直接发送mACTION_DEVICE_AVAILABLE
     */
    public void doReadBalance(){
        stopReadBalance = false;
        readBalance();
    }

    public void doStopReadBalance(){
        stopReadBalance = true;
        modeThread.interrupt();
        modeEnds();
    }

    /**
     * 检测设备是否靠近。
     * 使用RSSI值进行判断，默认阈值是-30，可在常量RSSI_NEAR中更改。默认超时时间为10s，可在RSSI_SCAN_PEROID和
     * RSSI_SCAN_COUNT中设置。
     * 若在超时时间内检测到RSSI大于默认阈值，则发送广播mACTION_DEVICE_NEAR，超时未检测则会发送广播
     * mACTION_DEVICE_NOT_NEAR。
     */
    public void doJudgeDeviceNear(){
        Log.i(TAG, "doJudgeDeviceNear() called.");
        isDeviceNearReported = false;
        stopJudge = false;
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(byte i = 0; i<RSSI_SCAN_COUNT; i++){
                    if(isDeviceNearReported) break;
                    if(stopJudge) break;
                    mBluetoothGatt.readRemoteRssi();
                    try {
                        Thread.sleep(RSSI_SCAN_PERIOD);
                    } catch (InterruptedException e) {

                    }
                }
                if(!isDeviceNearReported) broadcastUpdate(mACTION_DEVICE_NOT_NEAR);
            }
        }).start();
    }

    public void doStopJudgeDeviceNear(){
        stopJudge = true;
    }

////////////////////外部接口结束/////////////////////////////////////////////////////////////////////


    private final static String TAG = "BluetoothService";
    private int mConnectionState = STATE_DISCONNECTED;
    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    static private BluetoothGatt mBluetoothGatt;
    //private String deviceAddress = null;
    public boolean isPasswordCorrect = false;
    public boolean isTimeout = false;
    private boolean callbackFlag = true;
    private boolean isDeviceNearReported = false;
    private boolean stopJudge = false;
    private boolean stopReadBalance = false;

    private byte passwordArray[];

    //mode
    private static final byte MODE_VOID = -1;
    private static final byte MODE_CHECK_PASSWORD = 0;
    private static final byte MODE_SEND_IDCARD = 1;
    private static final byte MODE_READ_IDCARD = 2;
    private static final byte MODE_PUSH_NOTIFICATION = 3;
    private static final byte MODE_READ_NFC = 4;
    private static final byte MODE_CHANGE_PASSWORD = 5;

    //Bluetooth connection state
    public static final int STATE_DISCONNECTED = 0;
    public static final int STATE_CONNECTING = 1;
    public static final int STATE_CONNECTED = 2;
    //TOAST
    private static final int NOTIFICATION_GET = 1;
    private static final int READY = 2;

    private BluetoothGattCharacteristic FFF1 = null;
    private BluetoothGattCharacteristic FFF2 = null;
    private BluetoothGattCharacteristic FFF3 = null;
    private BluetoothGattCharacteristic FFF4 = null;
    private BluetoothGattCharacteristic FFF5 = null;
    private BluetoothGattCharacteristic FFF6 = null;
    private BluetoothGattCharacteristic FFF7 = null;
    private BluetoothGattCharacteristic FFF8 = null;
    private BluetoothGattCharacteristic FFF9 = null;


    ///////////////////////////////////////////服务相关//////////////////////////////////////////////////////////////////////

    @Override
    public void onCreate(){
        super.onCreate();
        Log.d(TAG, "BluetoothLeService created");
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
        disconnect();
        close();
        Log.d(TAG, "BluetoothLeService destroyed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startID){
        Log.d(TAG, "BluetoothLeService started");

        if(intent.getAction() != null){
            switch (intent.getAction()){
                case "PUSH_NOTIFICATION": {
                    if(isReady()){
                        pushNotification(intent.getByteExtra("NOTIFICATION_TYPE", (byte)0));
                    }else Log.i(TAG, "Push notification failed, connection: " + mConnectionState + ", mode: " + mode);
                } break;

                case "BLUETOOTH_STATE_ON": {
                    connectDevice();
                } break;

                case "CHECK_PASSWORD":
                    connectDevice(intent.getStringExtra("DEVICE_ADDRESS"), intent.getStringExtra("DEVICE_PASSWORD"));
                    break;

                default: break;
            }
        }

        return START_REDELIVER_INTENT;
    }

    public class LocalBinder extends Binder {
        public BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // After using a given device, you should make sure that BluetoothGatt.close() is called
        // such that resources are cleaned up properly.  In this particular example, close() is
        // invoked when the UI is disconnected from the Service.
        //close()
        return super.onUnbind(intent);
    }

    private final IBinder mBinder = new LocalBinder();


    /////////////////////////////////////////蓝牙基础相关//////////////////////////////////////////////////////////////////


    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    private boolean bluetoothInitialize() {
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        if (mBluetoothAdapter == null) {
            mBluetoothAdapter = mBluetoothManager.getAdapter();
            if (mBluetoothAdapter == null) {
                Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
                return false;
            }
        }

        if (!mBluetoothAdapter.isEnabled()) {
            broadcastUpdate(mACTION_REQUEST_ENABLE_BT);
            return false;
        }

        Log.d(TAG, "bluetoothInitialize succeed");
        return true;
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     *
     * @return Return true if the connection is initiated successfully. The connection result
     *         is reported asynchronously through the
     *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     *         callback.
     */
    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        broadcastUpdate(mACTION_DEVICE_CONNECTING);

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        Log.d(TAG, "Trying to create a new connection.");
        mConnectionState = STATE_CONNECTING;
        return true;
    }

    public boolean connectPrevious(){
        Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
        if (mBluetoothGatt.connect()) {
            mConnectionState = STATE_CONNECTING;
            return true;
        } else {
            return false;
        }
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        Log.d(TAG, "disconnect()");
        isPasswordCorrect = false;
        mBluetoothGatt.disconnect();

    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    private void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        BluetoothRW.done();
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
     *
     * @return A {@code List} of supported services.
     */
    private List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;

        return mBluetoothGatt.getServices();
    }

    //////////////////////////////////////蓝牙功能相关////////////////////////////////////////////////////////////////

    private void getChara(List<BluetoothGattService> gattServices){
        if (gattServices == null) return;
        String uuid;
        for (BluetoothGattService gattService : gattServices) {
            if(gattService.getUuid().toString().equalsIgnoreCase("0f0e0d0c-0b0a-0908-0706-050403020100")){
                List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
                for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                    uuid = gattCharacteristic.getUuid().toString();
                    //get certain characteristics
                    if(uuid.equalsIgnoreCase("1f1e1d1c-1b1a-1918-1716-151413121110")) {FFF1=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("2f2e2d2c-2b2a-2928-2726-252423222120")) {FFF2=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("3f3e3d3c-3b3a-3938-3736-353433323130")) {FFF3=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("4f4e4d4c-4b4a-4948-4746-454443424140")) {FFF4=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("5f5e5d5c-5b5a-5958-5756-555453525150")) {FFF5=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("6f6e6d6c-6b6a-6968-6766-656463626160")) {FFF6=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("7f7e7d7c-7b7a-7978-7776-757473727170")) {FFF7=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("8f8e8d8c-8b8a-8988-8786-858483828180")) {FFF8=gattCharacteristic;}
                    if(uuid.equalsIgnoreCase("9f9e9d9c-9b9a-9998-9796-959493929190")) {
                        FFF9=gattCharacteristic;
                        deviceInitialize();
                    }
                }
            }
        }
    }

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if(status == 0){
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    mConnectionState = STATE_CONNECTED;
                    broadcastUpdate(mACTION_DEVICE_CONNECTED);
                    //deviceAddress = gatt.getDevice().getAddress();
                    Log.i(TAG, "Connected to GATT server.");
                    Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    mConnectionState = STATE_DISCONNECTED;
                    broadcastUpdate(mACTION_DEVICE_DISCONNECTED);
                    Log.i(TAG, "Disconnected from GATT server.");
                    close();
                }
            }else if(status == 133){
                isTimeout = true;
                //todo 连接超时
                broadcastUpdate(mACTION_DEVICE_CONNECT_FAILED);
                broadcastUpdate(mACTION_DEVICE_DISCONNECTED);
                mConnectionState = STATE_DISCONNECTED;
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                getChara(getSupportedGattServices());
            } else if(status == 133){
                isTimeout = true;
            }else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {

            if (status == BluetoothGatt.GATT_SUCCESS) {
                BluetoothRW.done();
                if(FFF8.getUuid().equals(characteristic.getUuid())) readIDcardBuffer.handler(characteristic.getValue());
                if(FFF5.getUuid().equals(characteristic.getUuid())) readNFCBuffer(characteristic.getValue());
            } else if (status == 133) {
                isTimeout = true;
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS){
                BluetoothRW.done();
                if(FFF3.getUuid().equals(characteristic.getUuid())) modeThread.interrupt();
                //if(FFF7.getUuid().equals(characteristic.getUuid())) deviceReady();
            }else if(status == 133){
                isTimeout = true;
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            notificationHandler(characteristic.getValue());
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
                                      int status) {
            Log.d(TAG, "going to interruput iniThread");
            if(iniThread != null) iniThread.interrupt();
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){
            if(rssi > RSSI_NEAR) {
                broadcastUpdate(mACTION_DEVICE_NEAR);
                isDeviceNearReported = true;
            }

        }

    };

    private static class BluetoothRW{
        static boolean isBusy = false;

        static void done(){
            isBusy = false;
        }

        static void read(BluetoothGattCharacteristic chara){
            if(isBusy){
                Log.w(TAG, "BluetoothGatt is busy, read request of " + chara.getUuid().toString() + " is not done.");
            }else{
                isBusy = true;
                mBluetoothGatt.readCharacteristic(chara);
            }
        }

        static void write(BluetoothGattCharacteristic chara){
            if(isBusy){
                Log.w(TAG, "BluetoothGatt is busy, write request of " + chara.getUuid().toString() + " is not done.");
            }else{
                isBusy = true;
                mBluetoothGatt.writeCharacteristic(chara);
            }
        }

        static void write(byte[] bytes,BluetoothGattCharacteristic chara){
            chara.setValue(bytes);
            write(chara);
        }

        static void enableNotification(BluetoothGattCharacteristic chara){

            mBluetoothGatt.setCharacteristicNotification(chara, true);
            BluetoothGattDescriptor descriptor = chara.getDescriptor(
                    UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(descriptor);
        }
    }

    private Handler delayHandler = new Handler();
    private boolean mScanning = false;

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            delayHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    Log.d(TAG, "Stop scanning.");
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            Log.d(TAG, "Start scanning.");
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                    if(rssi > RSSI_NEAR) broadcastUpdate(mACTION_DEVICE_NEAR);
                }
            };

    //////////////////////////////////////基础功能//////////////////////////////////////////////////////////////////

    public void logState(){
        Log.i(TAG, "[STATE] CONNECTION=" + mConnectionState + " MODE=" + mode );
    }

    private Handler handler = new Handler(){
        public void handleMessage(Message msg){
            switch(msg.what){
                case NOTIFICATION_GET:
                    Toast.makeText(BluetoothLeService.this, "NOTIFICATION GET!", Toast.LENGTH_LONG).show();
                    break;
                case READY:
                    Toast.makeText(BluetoothLeService.this, "READY!", Toast.LENGTH_LONG).show();
                    break;

            }
        }
    };

    private void sendMessage(int msg){
        Message message = new Message();
        message.what = msg;
        handler.sendMessage(message);
    }

    private void broadcastUpdate(final String action) {
        Log.i(TAG, "Send broadcast: " + action);
        sendBroadcast(new Intent(action));
    }

    private Thread iniThread;
    private void deviceInitialize(){
        Log.i(TAG, "Device initializing...");
        iniThread = new Thread(new Runnable(){
            @Override
            public void run(){
                BluetoothRW.enableNotification(FFF4);
                try {
                    Thread.sleep(COMMON_TIMEOUT);
                } catch (InterruptedException e1) {
                    //BluetoothRW.write(passwordArray, FFF7);
                    checkPassword(passwordArray);
                }
            }
        });
        iniThread.start();
    }

    private void deviceReady(){
        Log.i(TAG, "Device ready.");
        broadcastUpdate(mACTION_DEVICE_READY);
    }

    private boolean isCallbackFlag(){
        if(callbackFlag){
            callbackFlag = false;
            return true;
        } else {
            return false;
        }
    }

    private void modeStarts(byte mode){
        this.mode = mode;
        broadcastUpdate(mACTION_BLUETOOTH_BUSY);
    }

    private void modeEnds(){
        mode = MODE_VOID;
        broadcastUpdate(mACTION_BLUETOOTH_AVAILABLE);
        if(ifNotificationUnpushed){
            ifNotificationUnpushed = false;
            pushNotification(notificationType);
            notificationType = 0;
        }
    }

    private void setCommand(byte command){
        Log.d(TAG, "set command " + String.valueOf(command));
        FFF1.setValue(new byte[]{command});
        BluetoothRW.write(FFF1);
    }

    private static class BytesHandler{

        static byte[] setHead(byte[] head,byte[] ori){
            byte[] headAdded = new byte[head.length + ori.length];
            System.arraycopy(head, 0, headAdded, 0, 1);
            System.arraycopy(ori, 0, headAdded, 1, ori.length);
            return headAdded;
        }

        static byte[] removeHead(byte[] ori){
            byte[] headRemoved = new byte[ori.length-1];
            System.arraycopy(ori, 1, headRemoved, 0, ori.length-1);
            return headRemoved;
        }

        static byte getHead(byte[] ori){
            return ori[0];
        }

        static byte[] hash(byte[] hashThis) {
            try {
                byte[] hash = new byte[16];
                MessageDigest md = MessageDigest.getInstance("MD5");
                hash = md.digest(hashThis);

                final StringBuilder hashBuilder = new StringBuilder(hash.length);
                for(byte byteChar : hash)
                    hashBuilder.append(String.format("%02X ", byteChar));
                Log.v(TAG, "hash length: " +  hash.length);
                Log.v(TAG, "hash data:" +  hashBuilder.toString());

                return hash;
            } catch (NoSuchAlgorithmException nsae) {
                System.err.println("MD5 algorithm is not available...");
                System.exit(2);
            }
            return null;
        }

        static byte[] int2bytes(int srcInt, int length){
            byte[] bytes = new byte[length];
            for (int i = 0; (i < 4) && (i < length); i++) {
                bytes[i] = (byte) (srcInt >> 8 * i & 0xFF);
            }
            return bytes;
        }

        static int bytes2int(byte[] bytes){
            int dstInt = 0;
            byte bLoop;

            for (int i = 0; i < bytes.length; i++) {
                bLoop = bytes[i];
                dstInt += (bLoop & 0xFF) << (8 * i);
            }
            return dstInt;
        }

        static String bytes2string(byte[] bytes){
            final StringBuilder builder = new StringBuilder(bytes.length);
            for(byte byteChar : bytes) builder.append(String.format("%02X ", byteChar));
            return builder.toString();

        }

        static byte[] string2bytes(String str){
            String[] stringArray = new String[str.length()];
            for(byte i=0; i<str.length(); i++){
                stringArray[i] = str.substring(i,i+1);
            }
                byte[] bytes = new byte[stringArray.length];
                for(byte i=0; i<stringArray.length; i++){
                    bytes[i] = Byte.parseByte(stringArray[i]);
                }
                return bytes;
        }

        static byte[] reverseBytes(byte[] array) {
            byte temp;
            for (int i = 0; i < array.length / 2; i++) {
                temp = array[i];
                array[i] = array[array.length - 1 - i];
                array[array.length - 1 - i] = temp;
            }
            return array;
        }
    }

    private static class SendIDcardBuffer {
        private static IDcard idcard = null;
        private static byte[] SerilizedIDcard;
        private static byte[] packed;
        private static byte COUNT;

        static void setBuffer(IDcard toSend){
            COUNT = 0;
            idcard = toSend;
            SerilizedIDcard = idcard.toBytes();
            packed =  new byte[SerilizedIDcard.length + 18];
            System.arraycopy(BytesHandler.int2bytes(SerilizedIDcard.length, 2),0, packed, 0, 2);
            System.arraycopy(BytesHandler.hash(SerilizedIDcard), 0, packed, 2, 16);
            System.arraycopy(SerilizedIDcard, 0, packed, 18, SerilizedIDcard.length);

            Log.v(TAG, "byte[] to send: " + BytesHandler.bytes2string(packed));
        }

        static byte[] getPack19(){
            if(COUNT*19 < packed.length){
                byte[] pack19 = new byte[19];
                try{
                    pack19= Arrays.copyOfRange(packed, COUNT*19, COUNT*19 + 19);
                }catch (ArrayIndexOutOfBoundsException e){
                    pack19 = Arrays.copyOfRange(packed, COUNT*19, packed.length-1);
                }finally {
                    COUNT++;
                }

                Log.v(TAG, "pack19: " + BytesHandler.bytes2string(pack19));
                return pack19;
            } else
                return null;
        }
    }

    ReadIDcardBuffer readIDcardBuffer;
    private class ReadIDcardBuffer{
        private byte count;
        private byte[] byteBuffer;
        private boolean ifContinue;
        public List<IDcard> idcards = new ArrayList<>();

        ReadIDcardBuffer(){
            count = 1;
            ifContinue = false;
        }

        void handler(byte[] inBytes){
            Log.v(TAG, "GET PACK: " + BytesHandler.bytes2string(inBytes));

            byte head = BytesHandler.getHead(inBytes);
            byte[] pack = BytesHandler.removeHead(inBytes);
            if(head == count){
                count++;
                if(head == (byte)1){
                    Log.d(TAG, "first pack get!");
                    byteBuffer = pack;
                } else{
                    Log.d(TAG, String.valueOf(head) + "th pack get!");
                    byte[] temp = new byte[byteBuffer.length + 19];
                    System.arraycopy(byteBuffer, 0, temp, 0, byteBuffer.length);
                    System.arraycopy(pack, 0, temp, byteBuffer.length, 19);
                    byteBuffer = temp;
                }
                ifContinue = true;
                if(modeThread != null) modeThread.interrupt();
            } else if(head == (byte)-1){
                get1IDcard();
                count = 1;
                ifContinue = true;
                if(modeThread != null) modeThread.interrupt();
            } else if(head == (byte)0){
                get1IDcard();
                ifContinue = false;
                if(modeThread != null) modeThread.interrupt();
            } else {
                Log.w(TAG, "Unknown head: " + String.valueOf(head) + ", current count is: " + String.valueOf(count));
            }
        }

        private void get1IDcard(){
            Log.v(TAG, "get byte[]: " + BytesHandler.bytes2string(byteBuffer));

            int length = BytesHandler.bytes2int(Arrays.copyOfRange(byteBuffer, 0, 2));
            byte[] hash = Arrays.copyOfRange(byteBuffer, 2, 18);
            byte[] serilizedIDcard = Arrays.copyOfRange(byteBuffer, 18, 18+length);

            if(Arrays.equals(hash, BytesHandler.hash(serilizedIDcard))){
                Log.d(TAG, "Hash check passed! Congratulations!");
                idcards.add(IDcard.toIDcard(serilizedIDcard));
            }
            byteBuffer = null;
        }

        public IDcard[] toIDcards(){
            IDcard[] idcards =
                    ((List<IDcard>) this.idcards).toArray(new IDcard[this.idcards.size()]);
            for(IDcard idcard : idcards){
                Log.i(TAG, "IDcard get: " + idcard.n + " " + idcard.p + " " + idcard.s);
            }
            return idcards;
        }

        boolean ifContinue(){
            if(ifContinue){
                BluetoothRW.write(new byte[]{2,1}, FFF3);
                try {
                    Thread.sleep(COMMON_WAIT_TIME);
                } catch (InterruptedException e) {
                    Log.d(TAG, "write F3 interrupted.");
                }
                return true;
            }else return false;
        }
    }

    private void readNFCBuffer(byte[] bytes){
        modeEnds();
        float balance = ( (float)BytesHandler.bytes2int(BytesHandler.reverseBytes(bytes)) )/100;
        Log.i(TAG, "NFC card balance read: " + balance);
        Intent intent = new Intent(mACTION_BALANCE_READ);
        intent.putExtra("balance", balance);
        sendBroadcast(intent);
    }

    private void notificationHandler(byte[] notification){
        if(notification.length != 2) return;
        Log.d(TAG, "NOTIFICATION: " + String.valueOf(notification[0]) + String.valueOf(notification[1]));
        if(notification[0] == mode){
            callbackFlag = (notification[1] == 1);
            if(modeThread != null) modeThread.interrupt();
            //if(mode == MODE_CHECK_PASSWORD)
        }
    }


    /////////////////////////////////////应用功能//////////////////////////////////////////////////////////////////

    private Thread modeThread;
    private byte mode = MODE_VOID;

    private void sendIDcard(){
        if(modeThread != null && modeThread.isAlive()){
            Log.w(TAG, String.valueOf(mode) + " mode running, request not done, return.");
            return;
        }
        modeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                modeStarts(MODE_SEND_IDCARD);
                Log.i(TAG, "写名片开始");

                setCommand(MODE_SEND_IDCARD);
                try {
                    Thread.sleep(COMMON_TIMEOUT);
                } catch (InterruptedException e) {
                    Log.d(TAG, "Interrupted when waiting for FFF4.");
                }
                if(isCallbackFlag()){
                    byte[] head = null;
                    byte[] pack = null;
                    for(byte counter = 1;counter<100;){
                        head = new byte[]{counter};
                        pack = SendIDcardBuffer.getPack19();
                        if(pack == null) break;

                        for(byte fireCounter = 0;fireCounter <MAX_WRITE_LOOP;fireCounter++) {
                            BluetoothRW.write(BytesHandler.setHead(head, pack), FFF8);

                            try {
                                Thread.sleep(COMMON_TIMEOUT);
                            } catch (InterruptedException e) {
                                Log.d(TAG, "Interrupted when waiting for FFF4.");
                                if(isCallbackFlag()){
                                    counter++;
                                    break;
                                }
                            }
                        }
                    }
                    BluetoothRW.write(new byte[20],FFF8);
                }

                modeEnds();
                Log.i(TAG, "写名片结束");
                broadcastUpdate(mACTION_IDCARD_WRITE);
            }
        });
        modeThread.start();
    }

    private void readIDcard(){
        readIDcardBuffer = new ReadIDcardBuffer();
        if(modeThread != null && modeThread.isAlive()){
            Log.w(TAG,  String.valueOf(mode) + " mode running, request not done, return.");
            return;
        }
        modeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                modeStarts(MODE_READ_IDCARD);
                Log.i(TAG, "读名片开始");

                setCommand(MODE_READ_IDCARD);
                try {
                    Thread.sleep(COMMON_TIMEOUT);
                } catch (InterruptedException e) {
                    Log.d(TAG, "Interrupted when waiting for FFF4.");
                }
                if(isCallbackFlag()){
                    do{
                        BluetoothRW.read(FFF8);

                        try {
                            Thread.sleep(COMMON_TIMEOUT);
                        } catch (InterruptedException e) {
                            Log.d(TAG, "Interrupted when waiting for FFF8.");
                        }
                    } while (readIDcardBuffer.ifContinue());
                }

                modeEnds();
                Log.i(TAG, "读名片结束");
                broadcastUpdate(mACTION_IDCARD_READ);
            }
        });
        modeThread.start();
    }

    private void readBalance(){
        if(modeThread != null && modeThread.isAlive()){
            Log.w(TAG, String.valueOf(mode) + " mode running, request not done, return.");
            return;
        }
        modeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                modeStarts(MODE_READ_NFC);
                setCommand(MODE_READ_NFC);
                boolean isCardRead = false;
                try {
                    Thread.sleep(COMMON_TIMEOUT);
                } catch (InterruptedException e) {
                    Log.d(TAG, "Interrupted when waiting for FFF4.");
                    if(!stopReadBalance) BluetoothRW.read(FFF5);
                    isCardRead = true;
                }
                if(!isCardRead) {
                    broadcastUpdate(mACTION_BALANCE_NOT_READ);
                    modeEnds();//若没有，则超时后结束；若有，则在读到余额后结束
                }
            }
        });
        modeThread.start();
    }

    private void checkPassword(final byte[] psw){
        if(modeThread != null && modeThread.isAlive()){
            Log.w(TAG, String.valueOf(mode) + " mode running, request not done, return.");
            return;
        }
        modeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                mode = MODE_CHECK_PASSWORD;

                BluetoothRW.write(psw, FFF7);
                try {
                    Thread.sleep(COMMON_TIMEOUT);
                } catch (InterruptedException e) {
                    Log.d(TAG, "Interrupted when waiting for FFF4.");
                    if(isCallbackFlag()){
                        Log.i(TAG, "Password correct!");
                        broadcastUpdate(mACTION_PASSWORD_CORRECT);
                        mode = MODE_VOID;
                        deviceReady();
                    } else {
                        Log.i(TAG, "Password wrong!");
                        broadcastUpdate(mACTION_PASSWORD_WRONG);
                        mode = MODE_VOID;
                        disconnect();
                    }
                }

            }
        });
        modeThread.start();
    }

    private void changePassword(final byte[] newPsw){
        if(modeThread != null && modeThread.isAlive()){
            Log.w(TAG, String.valueOf(mode) + " mode running, request not done, return.");
            return;
        }
        modeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                modeStarts(MODE_CHANGE_PASSWORD);

                BluetoothRW.write(newPsw, FFF6);
                //todo 没有加入等待FFF4的部分

                modeEnds();
            }
        });
        modeThread.start();
    }

    private boolean ifNotificationUnpushed = false;
    private byte notificationType = 0;

    private void pushNotification(final byte notificationType){
        if(notificationType == 0) return;

        if(modeThread != null && modeThread.isAlive()){
            ifNotificationUnpushed = true;
            this.notificationType = notificationType;
        } else{
            modeThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    modeStarts(MODE_PUSH_NOTIFICATION);

                    FFF9.setValue(new byte[]{notificationType});
                    BluetoothRW.write(FFF9);
                    //todo 没有加入等待FFF4的部分

                    modeEnds();
                }
            });
            modeThread.start();
        }
    }



    //todo 异常处理 包括设备非法断开连接
}